package com.ds.infrastructure.hyperspace.container.flow.listener;

import com.ds.infrastructure.hyperspace.common.EndpointNode;
import com.ds.infrastructure.hyperspace.common.TargetEndpointStream;
import com.ds.infrastructure.hyperspace.container.flow.context.HyperspaceDuplexFlowContext;
import com.ds.infrastructure.hyperspace.container.internal.NettyContainer;
import io.netty.handler.codec.http2.Http2Exception;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import static com.ds.infrastructure.hyperspace.container.internal.util.StringConstant.HYPHEN;
import static com.ds.infrastructure.hyperspace.monitor.EndpointType.GATEWAY;
import static com.ds.infrastructure.hyperspace.monitor.EndpointType.TARGET;

/**
 * @author  Weishaoying
 */
@Slf4j
public class DefaultHyperspaceEventListener implements HyperspaceEventListener<HyperspaceDuplexFlowContext> {

    private static final org.slf4j.Logger ROUTE_LOGGER = org.slf4j.LoggerFactory.getLogger("hyperspaceRoute");

    private static final String METRICS_ERROR_MESSAGE = "Record metrics failed!";

    private static final String UNKNOWN_SERVICE = "unknown";

    private static final String HTTP2_CODEC_EXCEPTION_SERVICE = "http2_codec_exception";

    private final NettyContainer nettyContainer;

    public DefaultHyperspaceEventListener(NettyContainer nettyContainer) {
        this.nettyContainer = nettyContainer;
    }

    @Override
    public void fireEntryInput(HyperspaceDuplexFlowContext duplexFlowContext, Object data) {
        // no implements now
    }

    @Override
    public void fireEntryInputComplete(HyperspaceDuplexFlowContext duplexFlowContext) {
        // no implements now
    }

    @Override
    public void fireTargetInput(HyperspaceDuplexFlowContext duplexFlowContext, Object data) {
        // no implements now
    }

    @Override
    public void fireTargetInputComplete(HyperspaceDuplexFlowContext duplexFlowContext) {
        // no implements now
    }

    @Override
    public void fireEntryOutput(HyperspaceDuplexFlowContext duplexFlowContext, Object data) {
        // no implements now
    }

    @Override
    public void fireEntryOutputComplete(HyperspaceDuplexFlowContext duplexFlowContext) {
        try {
            String tracerId = HYPHEN;
            if (duplexFlowContext.getTracerContext() != null) {
                tracerId = duplexFlowContext.getTracerContext().getTracerId();
                duplexFlowContext.getTracerContext().finished(duplexFlowContext, nettyContainer.getOpentracingReporter());
            }
            nettyContainer.getMetricsManager().incrementRequestCounter(
                    duplexFlowContext.getServiceName(),
                    String.valueOf(duplexFlowContext.getEntryResponseCode()),
                    String.valueOf(duplexFlowContext.getTargetResponseCode()));

            nettyContainer.getMetricsManager().incrementPayloadCounter(
                    duplexFlowContext.getServiceName(), duplexFlowContext.getPayloadSize());

            long totalCost = System.currentTimeMillis() - duplexFlowContext.getRequestStartTime();
            long targetCost = totalCost - duplexFlowContext.getGatewayCost();
            nettyContainer.getMetricsManager().changeRequestLatencyHistogram(
                    duplexFlowContext.getServiceName(), duplexFlowContext.getGatewayCost(),
                    targetCost);

            String targetNodeStr = HYPHEN;
            TargetEndpointStream targetStream = duplexFlowContext.getCurrentReferTargetEndpointStream();
            if (targetStream != null) {
                targetNodeStr = targetStream.getTargetEndpointNode().getHostInfo();
            }
            String grpcService = HYPHEN;
            if(duplexFlowContext.getGrpcServiceRouterName() != null){
                grpcService = duplexFlowContext.getGrpcServiceRouterName();
            }
            ROUTE_LOGGER.info(
                    "{\"requestStartTime\":\"{}\",\"gatewayResponseCode\":{},\"targetResponseCode\":{},\"protocol\":\"{}\"," +
                    " \"gatewayCost\":{},\"targetCost\":{},\"forwardedFor\":\"{}\",\"realIp\":\"{}\",\"entryNode\":\"{}\"," +
                    "\"targetNode\":\"{}\",\"targetRemoteAddress\":\"{}\",\"shardingType\":\"{}\",\"sharding\":\"{}\",\"zone\":\"{}\",\"payloadSize\":{}," +
                    "\"requestHost\":\"{}\",\"grpcService\":\"{}\",\"uri\":\"{}\",\"msg\":\"{}\",\"connectionCost\":{},\"pendingCost\":{},\"tracerId\":\"{}\"}",
                    duplexFlowContext.getRequestTimeStr(),
                    duplexFlowContext.getEntryResponseCode(),
                    duplexFlowContext.getTargetResponseCode(), duplexFlowContext.getEndpointProtocol(),
                    duplexFlowContext.getGatewayCost(), targetCost,
                    duplexFlowContext.getForwardedIp(), duplexFlowContext.getRealIp(),
                    duplexFlowContext.getClientIp(), targetNodeStr, duplexFlowContext.getTargetRemoteAddress(),
                    duplexFlowContext.getShardingKeyType(), duplexFlowContext.getShardingKey(), duplexFlowContext.getZoneId(),
                    duplexFlowContext.getPayloadSize(), duplexFlowContext.getHost(),grpcService,
                    duplexFlowContext.getUri(), StringUtils.remove(duplexFlowContext.getInterruptMessage(), "\""),
                    duplexFlowContext.getConnectionCost(), duplexFlowContext.getPendingCost(), tracerId);
        } catch (Exception e) {
            log.error(METRICS_ERROR_MESSAGE, e);
        }
    }

    @Override
    public void fireTargetOutput(HyperspaceDuplexFlowContext duplexFlowContext, Object data) {
        // no implements now
    }

    @Override
    public void fireTargetOutputComplete(HyperspaceDuplexFlowContext duplexFlowContext) {
        // no implements now
    }

    @Override
    public void fireEntryChannelActive(String endpointNamespace) {
        try {
            nettyContainer.getMetricsManager().incrementConnectionGauge(endpointNamespace, GATEWAY);
        } catch (Exception e) {
            log.error(METRICS_ERROR_MESSAGE, e);
        }
    }

    @Override
    public void fireEntryChannelInactive(String endpointNamespace) {
        try {
            nettyContainer.getMetricsManager().decrementConnectionGauge(endpointNamespace, GATEWAY);
        } catch (Exception e) {
            log.error(METRICS_ERROR_MESSAGE, e);
        }
    }

    @Override
    public void fireTargetChannelActive(EndpointNode endpointNode) {
        try {
            nettyContainer.getMetricsManager().incrementConnectionGauge(
                    endpointNode.getEndpoint().getNamespace(), TARGET);
        } catch (Exception e) {
            log.error(METRICS_ERROR_MESSAGE, e);
        }
    }

    @Override
    public void fireTargetChannelInactive(EndpointNode endpointNode) {
        try {
            nettyContainer.getMetricsManager().decrementConnectionGauge(
                    endpointNode.getEndpoint().getNamespace(), TARGET);
        } catch (Exception e) {
            log.error(METRICS_ERROR_MESSAGE, e);
        }
    }

    @Override
    public void fireEntryExceptionCaught(HyperspaceDuplexFlowContext duplexFlowContext, Throwable cause) {
        try {
            String serviceName;
            if (duplexFlowContext == null) {
                serviceName = cause instanceof Http2Exception ? HTTP2_CODEC_EXCEPTION_SERVICE:UNKNOWN_SERVICE;
            } else {
                serviceName = duplexFlowContext.getServiceName();
            }
            nettyContainer.getMetricsManager().incrementHyperspaceException(serviceName, GATEWAY);
        } catch (Exception e) {
            log.error(METRICS_ERROR_MESSAGE, e);
        }
    }

    @Override
    public void fireTargetExceptionCaught(String endpointNamespace, Throwable cause) {
        try {
            if (endpointNamespace == null) {
                endpointNamespace = UNKNOWN_SERVICE;
            }
            nettyContainer.getMetricsManager().incrementHyperspaceException(endpointNamespace, TARGET);
        } catch (Exception e) {
            log.error(METRICS_ERROR_MESSAGE, e);
        }
    }

    @Override
    public void firePendingRequestIncrement(String targetEndpointNamespace) {
        try {
            nettyContainer.getMetricsManager().incrementRequestPendingGauge(targetEndpointNamespace);
        } catch (Exception e) {
            log.error(METRICS_ERROR_MESSAGE, e);
        }
    }

    @Override
    public void firePendingRequestDecrement(String targetEndpointNamespace) {
        try {
            nettyContainer.getMetricsManager().decrementRequestPendingGauge(targetEndpointNamespace);
        } catch (Exception e) {
            log.error(METRICS_ERROR_MESSAGE, e);
        }
    }

    @Override
    public void fireTargetNodeStatusChange(String endpointNamespace, String nodeInfo, int status) {
        try {
            if (HYPHEN.equals(nodeInfo) || StringUtils.isBlank(nodeInfo)) {
                return;
            }
            nettyContainer.getMetricsManager().changeTargetNodeStatusGauge(endpointNamespace, nodeInfo, status);
        } catch (Exception e) {
            log.error(METRICS_ERROR_MESSAGE, e);
        }
    }

}
